 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.fieldassist;

 import org.eclipse.core.commands.AbstractHandler;
 import org.eclipse.core.commands.ExecutionEvent;
 import org.eclipse.core.commands.IHandler;
 import org.eclipse.jface.fieldassist.ContentProposalAdapter;
 import org.eclipse.jface.fieldassist.ControlDecoration;
 import org.eclipse.jface.fieldassist.FieldDecoration;
 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
 import org.eclipse.jface.fieldassist.IContentProposalProvider;
 import org.eclipse.jface.fieldassist.IControlContentAdapter;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.events.FocusEvent;
 import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.handlers.IHandlerActivation;
 import org.eclipse.ui.handlers.IHandlerService;
 import org.eclipse.ui.internal.WorkbenchMessages;
 import org.eclipse.ui.keys.IBindingService;

 /**
  * ContentAssistCommandAdapter extends {@link ContentProposalAdapter} to invoke
  * content proposals using a specified {@link org.eclipse.ui.commands.ICommand}.
  * The ability to specify a {@link org.eclipse.jface.bindings.keys.KeyStroke}
  * that explicitly invokes content proposals is hidden by this class, and
  * instead the String id of a command is used. If no command id is specified by
  * the client, then the default workbench content assist command is used.
  * <p>
  * As of 3.3, ContentAssistCommandAdapter can be optionally configured to
  * install the content assist decoration on its control.
  * <p>
  * This class is not intended to be subclassed.
  *
  * @since 3.2
  */
 public class ContentAssistCommandAdapter extends ContentProposalAdapter {

     private static final String CONTENT_ASSIST_DECORATION_ID = "org.eclipse.ui.fieldAssist.ContentAssistField"; //$NON-NLS-1$
 private String commandId;

     /**
      * The command id used for content assist. (value
      * <code>"org.eclipse.ui.edit.text.contentAssist.proposals"</code>)
      */
     public static final String CONTENT_PROPOSAL_COMMAND = "org.eclipse.ui.edit.text.contentAssist.proposals"; //$NON-NLS-1$

     // Default autoactivation delay in milliseconds
 // TODO: This should eventually be controlled by
 // a platform UI preference.
 private static final int DEFAULT_AUTO_ACTIVATION_DELAY = 500;

     private IHandlerService handlerService;

     private IHandlerActivation activeHandler;

     private IHandler proposalHandler = new AbstractHandler() {
         public Object execute(ExecutionEvent event) {
             openProposalPopup();
             return null;
         }

     };
     private ControlDecoration decoration;

     /**
      * Construct a content proposal adapter that can assist the user with
      * choosing content for the field. No visual indicator of content assist is
      * shown.
      *
      * @param control
      * the control for which the adapter is providing content assist.
      * May not be <code>null</code>.
      * @param controlContentAdapter
      * the <code>IControlContentAdapter</code> used to obtain and
      * update the control's contents as proposals are accepted. May
      * not be <code>null</code>.
      * @param proposalProvider
      * the <code>IContentProposalProvider</code> used to obtain
      * content proposals for this control, or <code>null</code> if
      * no content proposal is available.
      * @param commandId
      * the String id of the command that will invoke the content
      * assistant. If not supplied, the default value will be
      * "org.eclipse.ui.edit.text.contentAssist.proposals".
      * @param autoActivationCharacters
      * An array of characters that trigger auto-activation of content
      * proposal. If specified, these characters will trigger
      * auto-activation of the proposal popup, regardless of the
      * specified command id.
      */
     public ContentAssistCommandAdapter(Control control,
             IControlContentAdapter controlContentAdapter,
             IContentProposalProvider proposalProvider, String commandId,
             char[] autoActivationCharacters) {
         this(control, controlContentAdapter, proposalProvider, commandId,
                 autoActivationCharacters, false);
     }

     /**
      * Construct a content proposal adapter that can assist the user with
      * choosing content for the field.
      *
      * @param control
      * the control for which the adapter is providing content assist.
      * May not be <code>null</code>.
      * @param controlContentAdapter
      * the <code>IControlContentAdapter</code> used to obtain and
      * update the control's contents as proposals are accepted. May
      * not be <code>null</code>.
      * @param proposalProvider
      * the <code>IContentProposalProvider</code> used to obtain
      * content proposals for this control, or <code>null</code> if
      * no content proposal is available.
      * @param commandId
      * the String id of the command that will invoke the content
      * assistant. If not supplied, the default value will be
      * "org.eclipse.ui.edit.text.contentAssist.proposals".
      * @param autoActivationCharacters
      * An array of characters that trigger auto-activation of content
      * proposal. If specified, these characters will trigger
      * auto-activation of the proposal popup, regardless of the
      * specified command id.
      * @param installDecoration
      * A boolean that specifies whether a content assist control
      * decoration should be installed. The client is responsible for
      * ensuring that adequate space is reserved for the decoration.
      * Clients that want more fine-grained control of the
      * decoration's location or appearance should use
      * <code>false</code> for this parameter, creating their own
      * {@link ControlDecoration} and managing it directly.
      * @since 3.3
      */
     public ContentAssistCommandAdapter(Control control,
             IControlContentAdapter controlContentAdapter,
             IContentProposalProvider proposalProvider, String commandId,
             char[] autoActivationCharacters, boolean installDecoration) {
         super(control, controlContentAdapter, proposalProvider, null,
                 autoActivationCharacters);
         this.commandId = commandId;
         if (commandId == null) {
             this.commandId = CONTENT_PROPOSAL_COMMAND;
         }

         // If no autoactivation characters were specified, set them to the empty
 // array so that we don't get the alphanumeric auto-trigger of our
 // superclass.
 if (autoActivationCharacters == null) {
             this.setAutoActivationCharacters(new char[] {});
         }
         // Set a default autoactivation delay.
 setAutoActivationDelay(DEFAULT_AUTO_ACTIVATION_DELAY);

         // Add listeners to the control to manage activation of the handler
 addListeners(control);

         // Cache the handler service so we don't have to retrieve it each time
 this.handlerService = (IHandlerService) PlatformUI.getWorkbench()
                 .getService(IHandlerService.class);
         if (installDecoration) {
             // Note top left is used for compatibility with 3.2, although
 // this may change to center alignment in the future.
 decoration = new ControlDecoration(control, SWT.TOP | SWT.LEFT);
             decoration.setShowOnlyOnFocus(true);
             FieldDecoration dec = getContentAssistFieldDecoration();
             decoration.setImage(dec.getImage());
             decoration.setDescriptionText(dec.getDescription());
         }

     }

     /*
      * Add the listeners needed in order to activate the content assist command
      * on the control.
      */
     private void addListeners(Control control) {
         control.addFocusListener(new FocusListener() {
             public void focusLost(FocusEvent e) {
                 if (activeHandler != null) {
                     handlerService.deactivateHandler(activeHandler);
                     activeHandler = null;
                 }
             }

             public void focusGained(FocusEvent e) {
                 if (isEnabled()) {
                     if (activeHandler == null) {
                         activeHandler = handlerService.activateHandler(
                                 commandId, proposalHandler);
                     }
                 } else {
                     if (activeHandler != null) {
                         handlerService.deactivateHandler(activeHandler);
                     }
                     activeHandler = null;
                 }
             }
         });
         control.addDisposeListener(new DisposeListener() {
             public void widgetDisposed(DisposeEvent e) {
                 if (activeHandler != null) {
                     handlerService.deactivateHandler(activeHandler);
                     activeHandler = null;
                 }

             }
         });
     }

     /**
      * Return the string command ID of the command used to invoke content
      * assist.
      *
      * @return the command ID of the command that invokes content assist.
      */
     public String getCommandId() {
         return commandId;
     }

     /*
      * Return the field decoration that should be used to indicate that content
      * assist is available for a field. Ensure that the decoration text includes
      * the correct key binding.
      *
      * @return the {@link FieldDecoration} that should be used to show content
      * assist.
      *
      * @since 3.3
      */
     private FieldDecoration getContentAssistFieldDecoration() {
         FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
         // Look for a decoration installed for this particular command id.
 String decId = CONTENT_ASSIST_DECORATION_ID + getCommandId();
         FieldDecoration dec = registry.getFieldDecoration(decId);

         // If there is not one, base ours on the standard JFace one.
 if (dec == null) {
             FieldDecoration originalDec = registry
                     .getFieldDecoration(FieldDecorationRegistry.DEC_CONTENT_PROPOSAL);

             registry.registerFieldDecoration(decId, null, originalDec
                     .getImage());
             dec = registry.getFieldDecoration(decId);
         }
         // Always update the decoration text since the key binding may
 // have changed since it was last retrieved.
 IBindingService bindingService = (IBindingService) PlatformUI
                 .getWorkbench().getService(IBindingService.class);
         dec
                 .setDescription(NLS
                         .bind(
                                 WorkbenchMessages.ContentAssist_Cue_Description_Key,
                                 bindingService
                                         .getBestActiveBindingFormattedFor(getCommandId())));

         // Now return the field decoration
 return dec;
     }

     /*
      * (non-Javadoc)
      *
      * Overridden to hide and show the content assist decoration
      *
      * @see org.eclipse.jface.fieldassist.ContentProposalAdapter#setEnabled(boolean)
      * @since 3.3
      */
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
         if (decoration == null) {
             return;
         }
         if (enabled) {
             decoration.show();
         } else {
             decoration.hide();
         }
     }
 }

